/*-
 * Copyright (c) 2001 Dag-Erling Coïdan Smørgrav
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "linux/errno.h"
#include "linux/module.h"
#include "linux/seq_file.h"
#include "internal.h"
#include "stdio.h"

#ifdef   LOSCFG_FS_PROC

DEFINE_SPINLOCK(procfs_lock);

static struct proc_file g_proc_pf = {
    .f_pos         = 0,
};

static struct proc_dir_entry g_proc_root_dir_entry = {
    .namelen    = 5,
    .mode       = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH,
    .count      = ATOMIC_INIT(1),
    .proc_fops  = (const struct proc_file_operations *)NULL,
    .parent     = &g_proc_root_dir_entry,
    .name       = "/proc",
    .subdir     = (struct proc_dir_entry *)NULL,
    .next       = (struct proc_dir_entry *)NULL,
    .pf         = &g_proc_pf,
};

static loff_t proc_default_lseek(struct proc_file *procfile, loff_t off, int orig)
{
    loff_t ret = -EINVAL;
    switch (orig) {
        case SEEK_CUR:
            off += procfile->f_pos;
        /*lint -fallthrough*/
        case SEEK_SET:
            if (off < 0 || off > MAX_NON_LFS) {
                break;
            }
            procfile->f_pos = ret = off;
    }
    return ret;
}

static ssize_t __proc_file_read(struct proc_file *procfile, char __user *buf, size_t nbytes,
           loff_t *pos)
{
    ssize_t ret = 0;
    ssize_t size = 0;
    pfs_node * pn = (pfs_node *)NULL;
    unsigned long long p;

    if (NULL == pos) {
        p = 0;
    } else {
        p = *pos;
    }

    if (p > MAX_NON_LFS) {
        return 0;
    }

    if (nbytes > MAX_NON_LFS - p) {
        nbytes = MAX_NON_LFS - p;
    }

    pn = procfile->pPDE;
    for (; nbytes > 0;) {
        if (pn->read_proc != NULL) {
            size = (ssize_t)pn->read_proc(procfile, (void *)buf);
        }
        if (size == 0) {  /* end of file */
            break;
        }
        if (size < 0) { /* error */
            if (ret == 0) {
                ret = size;
            }
            break;
        }
        nbytes -= size;
        buf += size;
        ret += size;

        if (nbytes < size) {  /*lint !e574*/
            break;
        }
    }
    return ret;
}

static ssize_t proc_default_read(struct proc_file *procfile, char  *buf, size_t nbytes, loff_t *pos)
{
    pfs_node *de = (pfs_node *)(procfile->pPDE);

    spin_lock(&de->pde_unload_lock);
    if (de->proc_fops == NULL) {
        spin_unlock(&de->pde_unload_lock);
        return -EIO;
    }
    spin_unlock(&de->pde_unload_lock);

    return __proc_file_read(procfile, buf, nbytes, pos);
}


static ssize_t proc_default_write(struct proc_file *procfile, const char  *buffer, size_t count, loff_t *pos)
{
    pfs_node *de = (pfs_node *)(procfile->pPDE);

    if (de->write_proc != NULL) {
        spin_lock(&de->pde_unload_lock);
        if (de->proc_fops == NULL) {
            spin_unlock(&de->pde_unload_lock);
            return -EIO;
        }
        spin_unlock(&de->pde_unload_lock);

        return de->write_proc(procfile, buffer, count, pos);
    }
    return -EIO;
}

static struct proc_file_operations g_proc_default_operations = {
    .llseek     = proc_default_lseek,
    .read       = proc_default_read,
    .write      = proc_default_write,
};


int proc_match(unsigned int len, const char *name, pfs_node *pde)
{
    if (len != pde->namelen)
        return 0;
    return !strncmp(name, pde->name, len);
}


static pfs_node *pfs_find_node(pfs_node *parent, const char *name)
{
    pfs_node *pn = (pfs_node *)NULL;
    int length;

    if (parent == NULL || name == NULL) {
        return pn;
    }
    length = strlen(name);

    for (pn = parent->subdir; pn != NULL; pn = pn->next) {
        if ((length ==  pn->namelen) && strcmp(pn->name, name) == 0) {
            break;
        }
    }

    return pn;
}

/*
* descrition: find the file's handle
* pcPath: the file of fullpath
* return: the file of handle
* add by ll
*/
pfs_node *proc_find_entry(const char *pcPath)
{
    pfs_node *pHandle = (pfs_node *)NULL;
    int isFoundSub = 0;
    const char *next = (const char *)NULL;
    unsigned int len;
    int nLevelTotal = 0;
    int nLevelCount = 0;
    const char *p = (const char *)NULL;
    const char *sName = pcPath;


    while ((p = strchr(sName, '/')) != NULL) {
        nLevelTotal++;
        sName = p;
        sName++;
    }
    if (nLevelTotal < 1) {
        return pHandle;
    }

    pHandle = &g_proc_root_dir_entry;

    while ((pHandle != NULL) && (nLevelCount < nLevelTotal)) {
        nLevelCount++;
        isFoundSub = 0;
        while (pHandle != NULL) {
            next = strchr(pcPath, '/');
            if (next == NULL) {
                while (pHandle != NULL) {
                    if (strcmp(pcPath, pHandle->name) == 0) {
                        return pHandle;
                    }
                    pHandle = pHandle->next;
                }
                pHandle = (pfs_node *)NULL;
                return pHandle;
            }

            len = next - pcPath;
            if (pHandle == &g_proc_root_dir_entry) {
                if (nLevelCount == nLevelTotal) {
                    return pHandle;
                }
                len = g_proc_root_dir_entry.namelen;
            }
            if (proc_match(len, pcPath, pHandle)) {
                isFoundSub = 1;
                pcPath += len + 1;
                break;
            }

            pHandle = pHandle->next;
        }

        if (isFoundSub == 1) {
            pHandle = pHandle->subdir; /*lint !e613*/
        } else {
            pHandle = (pfs_node *)NULL;
            return pHandle;
        }
    }
    return (pfs_node *)NULL;
}

static int check_proc_name(const char *name, pfs_node **parent, const char **lastname)
{
    pfs_node *pn = *parent;
    const char *segment = name;
    const char *restname = (const char *)NULL;
    int length;

    if (pn == NULL) {
        pn = &g_proc_root_dir_entry;
    }

    spin_lock(&procfs_lock);

    restname = strchr(segment, '/');
    for (; restname != NULL; restname = strchr(segment, '/')) {
        length = restname - segment;
        for (pn = pn->subdir; pn != NULL ; pn = pn->next) {
            if (proc_match(length, segment, pn))
                break;
        }
        if (pn == NULL) {
            PRINT_ERR(" Error!No such name '%s'\n", name);
            spin_unlock(&procfs_lock);
            return -ENOENT;
        }
        segment = restname;
        segment++;

    }
    *lastname = segment;
    *parent = pn;
    spin_unlock(&procfs_lock);

    return 0;
}

static pfs_node *pfs_alloc_node(pfs_node **parent, const char *name, mode_t mode)
{
    pfs_node *pn = (pfs_node *)NULL;
    const char *lastname = (const char *)NULL;
    int ret;

    if (name == NULL || strlen(name) == 0) {
        return pn;
    }

    if (check_proc_name(name, parent, &lastname) != 0) {
        return pn;
    }

    if (strlen(lastname) > MAX_NAMELEN) {
        return pn;
    }

    if (S_ISDIR((*parent)->mode) == 0 || (strchr(lastname, '/'))) {
        return pn;
    }

    pn = (pfs_node *)malloc(sizeof(pfs_node));
    if (pn == NULL) {
        return (pfs_node *)NULL;
    }

    if ((mode & S_IALLUGO) == 0) {
        mode |= S_IRUSR | S_IRGRP | S_IROTH;
    }

    (void)memset_s(pn, sizeof(pfs_node), 0, sizeof(pfs_node));
    pn->namelen = strlen(lastname);
    pn->mode = mode;
    ret = memcpy_s(pn->name, sizeof(pn->name), lastname, strlen(lastname) + 1);
    if (ret != 0) {
        free(pn);
        return (pfs_node *)NULL;
    }

    pn->pf = (struct proc_file *)malloc(sizeof(struct proc_file));
    if (pn->pf == NULL) {
        free(pn);
        return (pfs_node *)NULL;
    }
    (void)memset_s(pn->pf, sizeof(struct proc_file), 0, sizeof(struct proc_file));
    pn->pf->pPDE = pn;
    ret = memcpy_s(pn->pf->name, sizeof(pn->pf->name), pn->name, pn->namelen + 1);
    if (ret != 0) {
        free(pn->pf);
        free(pn);
        return (pfs_node *)NULL;
    }

    (void)atomic_set(&pn->count, 1);
    spin_lock_init(&pn->pde_unload_lock); /*lint !e438*/
    return pn;
}

/*lint -e613*/
static int pfs_add_node(pfs_node *parent, pfs_node *pn)
{
    pfs_node *temp = (pfs_node *)NULL;

    if (parent == NULL) {
        PRINT_ERR("%s(): parent is NULL", __FUNCTION__);
        return -EINVAL;
    }

    if (pn->parent != NULL) {
        PRINT_ERR("%s(): node already has a parent", __FUNCTION__);
        return -EINVAL;
    }

    if (S_ISDIR(parent->mode) == 0) {
        PRINT_ERR("%s(): parent is not a directory", __FUNCTION__);
        return -EINVAL;
    }

    if (S_ISREG(pn->mode) && (pn->proc_fops == NULL)) {
        pn->proc_fops = &g_proc_default_operations;
    }

    spin_lock(&procfs_lock);

    temp = pfs_find_node(parent, pn->name);
    if (temp != NULL) {
        PRINT_ERR("Error!proc_dir_entry '%s/%s' already registered\n", parent->name, pn->name);
        spin_unlock(&procfs_lock);
        return -EEXIST;
    }

    pn->parent = parent;
    pn->next = parent->subdir;
    parent->subdir = pn;

    spin_unlock(&procfs_lock);

    return 0;

} /*lint +e613*/


static void pfs_detach_node(pfs_node *pn)
{
    pfs_node *parent = pn->parent;
    pfs_node **iter = (pfs_node **)NULL;

    if (parent == NULL) {
        PRINT_ERR("%s(): node has no parent", __FUNCTION__);
    }

    iter = &parent->subdir; /*lint !e613*/
    while (*iter != NULL) {
        if (*iter == pn) {
            *iter = pn->next;
            break;
        }
        iter = &(*iter)->next;
    }
    pn->parent = NULL;
}

static pfs_node *pfs_create_dir(pfs_node *parent, const char *name,
    const struct proc_file_operations *proc_fops, mode_t mode)
{
    pfs_node *pn = (pfs_node *)NULL;
    int ret;

    pn = pfs_alloc_node(&parent, name, S_IFDIR | mode);
    if (pn == NULL) {
        return pn;
    }
    pn->proc_fops = proc_fops;
    ret = pfs_add_node(parent, pn);
    if (ret != 0) {
        free(pn->pf);
        free(pn);
        return (pfs_node *)NULL;
    }

    return pn;
}


static pfs_node *pfs_create_file(pfs_node *parent, const char *name,
    const struct proc_file_operations *proc_fops, mode_t mode)
{
    pfs_node *pn = (pfs_node *)NULL;
    int ret;

    pn = pfs_alloc_node(&parent, name, S_IFREG | mode);
    if (pn == NULL) {
        return pn;
    }

    pn->proc_fops = proc_fops;
    ret = pfs_add_node(parent, pn);
    if (ret != 0) {
        free(pn->pf);
        free(pn);
        return (pfs_node *)NULL;
    }

    return pn;
}

struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent)
{
    pfs_node *pde = (pfs_node *)NULL;

    if (S_ISDIR(mode)) {
        pde = pfs_create_dir(parent, name, NULL, mode);
    } else {
        pde = pfs_create_file(parent, name, NULL, mode);
    }
    return pde;
}

static void free_proc_entry(pfs_node *pn)
{
    if (pn == NULL) {
        return;
    }
    if ((pn->pf != NULL) && (pn->pf->private_data != NULL)) {
        free(pn->pf->private_data);
        pn->pf->private_data = NULL;
    }
    if (pn->pf != NULL) {
        free(pn->pf);
        pn->pf = (struct proc_file *)NULL;
    }
    free(pn);

}

void pde_put(pfs_node *de)
{
    if (atomic_dec_and_test(&de->count))  /*lint !e64*/
        free_proc_entry(de);
}


static void __remove_proc_entry(pfs_node *pHandle)
{
    if (pHandle == NULL) {
        return;
    }
    __remove_proc_entry(pHandle->next);
    __remove_proc_entry(pHandle->subdir);

    pde_put(pHandle);
}

void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
{
    pfs_node *pn = (pfs_node *)NULL;
    const char *lastName = name;
    if (name == NULL || strlen(name) == 0) {
        return;
    }

    if (check_proc_name(name, &parent, &lastName) != 0) {
        return;
    }

    spin_lock(&procfs_lock);

    pn = pfs_find_node(parent, lastName);
    if (pn == NULL) {
        PRINT_ERR("Error:name '%s' not found!\n", name);
        spin_unlock(&procfs_lock);
        return;
    }
    pfs_detach_node(pn);

    spin_unlock(&procfs_lock);

    __remove_proc_entry(pn->subdir);
    pde_put(pn);
}

struct proc_dir_entry *proc_mkdir_mode(const char *name, mode_t mode, struct proc_dir_entry *parent)
{
    return pfs_create_dir(parent, name, NULL, mode);
}

struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
{
    return pfs_create_dir(parent, name, NULL, 0);
}

struct proc_dir_entry *proc_create_data(const char *name, mode_t mode, struct proc_dir_entry *parent,
    const struct proc_file_operations *proc_fops, void *data)
{
    pfs_node *pde = (pfs_node *)NULL;
    pde = create_proc_entry(name, mode, parent);
    if (pde != NULL) {
        if (proc_fops != NULL) {
            pde->proc_fops = proc_fops;
        }
        pde->data = data;
    }
    return pde;
}

struct proc_dir_entry *proc_create(const char *name, mode_t mode,
    struct proc_dir_entry *parent, const struct proc_file_operations *proc_fops)
{
    return proc_create_data(name, mode, parent, proc_fops, NULL);
}

static pfs_node *__proc_open(const char *filename, int flags, ...)
{
    pfs_node *pHandle = (pfs_node *)NULL;

    pHandle = proc_find_entry(filename);
    if (pHandle == NULL) {
        return (pfs_node *)NULL;
    }
    if (S_ISREG(pHandle->mode) && (pHandle->count != 1)) {
        return (pfs_node *)NULL;
    }
    pHandle->flags |= flags;
    (void)atomic_set(&pHandle->count, 2);
    if (S_ISREG(pHandle->mode) && (pHandle->proc_fops != NULL) && (pHandle->proc_fops->open != NULL)) {
        (void)pHandle->proc_fops->open((struct inode *)pHandle, pHandle->pf);
    }
    if (S_ISDIR(pHandle->mode)) {
        pHandle->pdir_current = pHandle->subdir;
        pHandle->pf->f_pos = 0;
    }

    return pHandle;
}

struct proc_dir_entry *proc_open(const char *filename, int flags, ...)
{
    pfs_node *pHandle = (pfs_node *)NULL;

    spin_lock(&procfs_lock);

    pHandle = __proc_open(filename, flags);

    spin_unlock(&procfs_lock);

    return pHandle;
}

int proc_close(struct proc_dir_entry *pHandle)
{
    int result = 0;

    if (pHandle == NULL) {
        return -1;
    }
    pHandle->pf->f_pos = 0;
    (void)atomic_set(&pHandle->count, 1);
    if (S_ISDIR(pHandle->mode)) {
        pHandle->pdir_current = pHandle->subdir;
    }
    if ((pHandle->proc_fops != NULL) && (pHandle->proc_fops->release != NULL)) {
        result = pHandle->proc_fops->release((struct inode *)pHandle,pHandle->pf);
        pHandle->pf->private_data = NULL;
    }
    return result;
}

static int __proc_readdir(pfs_node *pHandle, void *buf, size_t len)
{
    char *p = (char *)buf;
    int  ret;

    if (!pHandle->pdir_current) {
        *p = '\0';
        return -ENOENT;
    }
    ret = memcpy_s(p, len, pHandle->pdir_current->name, pHandle->pdir_current->namelen);
    if (ret != 0) {
        return -ENAMETOOLONG;
    }

    pHandle->pdir_current = pHandle->pdir_current->next;
    pHandle->pf->f_pos++;
    return ENOERR;
}


static int __proc_read(pfs_node *pHandle, void *buf, size_t len)
{
    int result = -1;

    if ((pHandle->proc_fops != NULL) && (pHandle->proc_fops->read != NULL)) {
        result = pHandle->proc_fops->read(pHandle->pf, (char *)buf, len, &(pHandle->pf->f_pos));
    }

    return result;
}

int proc_read(struct proc_dir_entry * pHandle, void *buf, size_t len)
{
    int result = -1;

    if (pHandle == NULL) {
        return result;
    }
    if (S_ISREG(pHandle->mode)) {
        result = __proc_read(pHandle, buf, len);
    } else if (S_ISDIR(pHandle->mode)) {
        result = __proc_readdir(pHandle,buf,len);
    }
    return result;

}

static int __proc_write(pfs_node *pHandle, const void *buf, size_t len)
{
    int result = -1;

    if (S_ISDIR(pHandle->mode)) {
        return result;
    }
    if ((pHandle->proc_fops != NULL) && (pHandle->proc_fops->write != NULL)) {
        result = pHandle->proc_fops->write(pHandle->pf, (const char  *)buf, len, &(pHandle->pf->f_pos));
    }
    return result;
}

int proc_write(struct proc_dir_entry *pHandle, const void *buf, size_t len)
{
    int result = -1;

    if (pHandle == NULL) {
        return result;
    }
    spin_lock(&procfs_lock);

    result = __proc_write(pHandle, buf, len);

    spin_unlock(&procfs_lock);

    return result;
}

loff_t proc_lseek(struct proc_dir_entry *pHandle, loff_t offset, int whence)
{
    loff_t result = 0;

    if ((pHandle != NULL) && (pHandle->proc_fops != NULL) && (pHandle->proc_fops->llseek != NULL)) {
        result = pHandle->proc_fops->llseek(pHandle->pf,offset,whence);
    }
    return result;
}

int proc_dirlseek(struct proc_dir_entry *pHandle, off_t * pos, int whence)
{
    // Only allow SEEK_SET to zero
    if (whence != SEEK_SET || *pos != 0) {
        return EINVAL;
    }
    pHandle->pdir_current = pHandle->subdir;
    pHandle->pf->f_pos = 0;
    return ENOERR;
}

int proc_stat(const char *file_ptr, struct proc_stat *buf)
{
    pfs_node *pHandle = (pfs_node *)NULL;
    int len = sizeof(buf->name);
    int ret;

    pHandle = proc_find_entry(file_ptr);
    if (pHandle == NULL) {
        return ENOENT;
    }

    ret = strncpy_s(buf->name, len, pHandle->name, len-1);
    if (ret != 0) {
        return ENAMETOOLONG;
    }

    buf->name[len-1] = '\0';
    buf->st_mode = pHandle->mode;
    buf->pPDE = pHandle;

    return 0;
}
#endif
